package com.carbohydrate42.kotlin_compose_demo.module

import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animate
import androidx.compose.animation.core.tween
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.MutatorMutex
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.min
import kotlin.math.roundToInt

/**
 * 重新改进一下 下拉刷新/上拉加载 列表, 重新抄一个,
 * https://juejin.cn/post/7141312829042688008
 *
 * 1. 主要是上啦加载的时候体验不是很好 (已解决)
 * todo 2. 初始化的时候, 或者说能够程序化的控制下拉刷新
 * todo 3. 列表拉到头的时候会有一个阴影, 这个阴影怎么去掉
 */

const val NONE = 0

const val TOP = 1 //indicator在顶部

const val BOTTOM = 2 //indicator在底部

@Immutable
data class SwipeProgress(
    val location: Int = NONE,//是在顶部还是在底部
    val offset: Float = 0f,//可见indicator的高度
    /*@FloatRange(from = 0.0, to = 1.0)*/
    val fraction: Float = 0f //0到1，0： indicator不可见   1：可见indicator的最大高度
)


//常规状态
const val NORMAL = 0

//下拉刷新状态
const val REFRESHING = 1

//上拉加载状态
const val LOADING_MORE = 2


@Stable
class MySwipeRefreshState(
    loadState: Int,
) {
    private val _indicatorOffset = Animatable(0f)
    private val mutatorMutex = MutatorMutex()

    var loadState: Int by mutableStateOf(loadState)

    /**
     * Whether a swipe/drag is currently in progress.
     */
    var isSwipeInProgress: Boolean by mutableStateOf(false)
        internal set

    //上下拉的偏移量等等
    var progress: SwipeProgress by mutableStateOf(SwipeProgress())
        internal set

    /**
     * The current offset for the indicator, in pixels.
     */
    internal val indicatorOffset: Float get() = _indicatorOffset.value

    internal suspend fun animateOffsetTo(
        offset: Float,
    ) {
        mutatorMutex.mutate {
            _indicatorOffset.animateTo(offset)
        }
    }

    /**
     * Dispatch scroll delta in pixels from touch events.
     */
    internal suspend fun dispatchScrollDelta(
        delta: Float,
        location: Int,
        maxOffsetY: Float,
    ) {
        mutatorMutex.mutate(MutatePriority.UserInput) {
            _indicatorOffset.snapTo(_indicatorOffset.value + delta)
            updateProgress(
                location = location,
                maxOffsetY = maxOffsetY,
            )
        }
    }

    /**
     * 更新progress
     * @param maxOffsetY  下拉或者上拉indicator最大高度
     */
    @OptIn(ExperimentalMaterialApi::class)
    private fun updateProgress(
        offsetY: Float = abs(indicatorOffset),
        location: Int,
        maxOffsetY: Float,
    ) {
        val offsetPercent = min(1f, offsetY / maxOffsetY)

        val offset = min(maxOffsetY, offsetY)
        progress = SwipeProgress(location, offset, offsetPercent)
    }
}

@Composable
fun rememberMySwipeRefreshState(loadState: Int): MySwipeRefreshState {
    return remember {
        MySwipeRefreshState(loadState)
    }
}


private const val DragMultiplier = 0.5f

private class SwipeRefreshNestedScrollConnection(
    private val state: MySwipeRefreshState,
    private val coroutineScope: CoroutineScope,
    private val onRefresh: () -> Unit,
    private val onLoadMore: () -> Unit
) : NestedScrollConnection {
    var refreshEnabled: Boolean = false//是否开启下拉刷新
    var loadMoreEnabled: Boolean = false//是否开启上拉加载
    var refreshTrigger: Float = 100f//最大的下上拉的距离
    var indicatorHeight: Float = 50f//顶部、底部下上组合项的高度

    private var isTop = false //是否是顶部的下拉
    private var isBottom = false//是否是达到

    override fun onPreScroll(
        available: Offset,
        source: NestedScrollSource
    ): Offset = when {
        //刷新和更多都禁用 则不处理
        !refreshEnabled && !loadMoreEnabled -> Offset.Zero
        //当处于刷新状态或者更多状态，不处理
        state.loadState != NORMAL -> Offset.Zero
        source == NestedScrollSource.Drag -> {
            Log.v("hj", "onPreScroll available = $available")
            if (available.y > 0 && isBottom) {
                onScroll(available)
            } else if (available.y < 0 && isTop) {
                onScroll(available)
            } else {
                Offset.Zero
            }
        }

        else -> Offset.Zero
    }

    override fun onPostScroll(
        consumed: Offset,
        available: Offset,
        source: NestedScrollSource
    ): Offset {
        //刷新和更多都禁用 则不处理
        if (!refreshEnabled && !loadMoreEnabled) {
            return Offset.Zero
        }
        //当处于刷新状态或者更多状态，不处理
        else if (state.loadState != NORMAL) {
            return Offset.Zero
        } else if (source == NestedScrollSource.Drag) {
            Log.d("hj", "onPostScroll available = $available , consumed = $consumed")
            if (available.y < 0) {
                if (!isBottom) {
                    isBottom = true
                }
                if (isBottom) {
                    return onScroll(available)
                }

            } else if (available.y > 0) {
                if (!isTop) {
                    isTop = true
                }
                if (isTop) {
                    return onScroll(available)
                }
            }
        }
        return Offset.Zero
    }

    private fun onScroll(available: Offset): Offset {
        if (!isBottom && !isTop) {
            return Offset.Zero
        }
        if (available.y > 0 && isTop) {
            state.isSwipeInProgress = true
        } else if (available.y < 0 && isBottom) {
            state.isSwipeInProgress = true
        } else if (state.indicatorOffset.roundToInt() == 0) {
            state.isSwipeInProgress = false
        }

        val newOffset = (available.y * DragMultiplier + state.indicatorOffset).let {
            if (isTop) it.coerceAtLeast(0f) else it.coerceAtMost(0f)
        }
        val dragConsumed = newOffset - state.indicatorOffset

        return if (dragConsumed.absoluteValue >= 0.5f) {
            coroutineScope.launch {
                state.dispatchScrollDelta(
                    dragConsumed,
                    if (isTop) TOP else BOTTOM,
                    refreshTrigger,
                )
            }
            // Return the consumed Y
            Offset(x = 0f, y = dragConsumed / DragMultiplier)
        } else {
            Offset.Zero
        }
    }

    override suspend fun onPreFling(available: Velocity): Velocity {
        // If we're dragging, not currently refreshing and scrolled
        // past the trigger point, refresh!
        if (state.loadState == NORMAL && abs(state.indicatorOffset) >= indicatorHeight) {
            if (isTop) {
                onRefresh()
            } else if (isBottom) {
                onLoadMore()
            }
        }

        // Reset the drag in progress state
        state.isSwipeInProgress = false

        // Don't consume any velocity, to allow the scrolling layout to fling
        return Velocity.Zero
    }

    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
        return Velocity.Zero.also {
            isTop = false
            isBottom = false
        }
    }
}

@Composable
fun BoxScope.LoadingIndicatorSample(
    modifier: Modifier,
    state: MySwipeRefreshState,
    indicatorHeight: Dp
) {
    val height = max(30.dp, with(LocalDensity.current) {
        state.progress.offset.toDp()
    })
    Box(
        modifier
            .fillMaxWidth()
            .height(height),
        contentAlignment = Alignment.Center
    ) {
        if (state.isSwipeInProgress) {
            if (state.progress.offset <= with(LocalDensity.current) { indicatorHeight.toPx() }) {
                Text(text = if (state.progress.location == TOP) "下拉刷新" else "上拉加载更多")
            } else {
                Text(text = if (state.progress.location == TOP) "松开刷新" else "松开加载更多")
            }
        } else {
            // AnimatedVisibility(state.loadState == REFRESHING || state.loadState == LOADING_MORE) {
            //     //加载中
            //     // CircularProgressIndicator()
            //     Text(text = if (state.loadState == REFRESHING) "正在刷新" else "正在加载更多")
            // }
            Text(text = if (state.loadState == REFRESHING) "正在刷新..." else "正在加载更多...")
        }
    }
}


@Composable
fun MySwipeRefresh(
    state: MySwipeRefreshState,
    onRefresh: () -> Unit,//下拉刷新回调
    onLoadMore: () -> Unit,//上拉加载更多回调
    modifier: Modifier = Modifier,
    refreshTriggerDistance: Dp = 120.dp,//indication可见的最大高度
    indicationHeight: Dp = 56.dp,//indication的高度
    refreshEnabled: Boolean = true,//是否支持下拉刷新
    loadMoreEnabled: Boolean = true,//是否支持上拉加载更多
    indicator: @Composable BoxScope.(modifier: Modifier, state: MySwipeRefreshState, indicatorHeight: Dp) -> Unit = { m, s, height ->
        LoadingIndicatorSample(m, s, height)
    },//顶部或者底部的Indicator
    content: @Composable (modifier: Modifier) -> Unit,
) {
    val refreshTriggerPx = with(LocalDensity.current) { refreshTriggerDistance.toPx() }
    val indicationHeightPx = with(LocalDensity.current) { indicationHeight.toPx() }

    // Our LaunchedEffect, which animates the indicator to its resting position
    LaunchedEffect(state.isSwipeInProgress) {
        if (!state.isSwipeInProgress) {
            // If there's not a swipe in progress, rest the indicator at 0f
            state.animateOffsetTo(0f)
        }
    }

    val coroutineScope = rememberCoroutineScope()
    val updatedOnRefresh = rememberUpdatedState(onRefresh)
    val updatedOnLoadMore = rememberUpdatedState(onLoadMore)

    val nestedScrollConnection = remember(state, coroutineScope) {
        SwipeRefreshNestedScrollConnection(
            state,
            coroutineScope,
            onRefresh = { updatedOnRefresh.value.invoke() },
            onLoadMore = { updatedOnLoadMore.value.invoke() }
        )
    }.apply {
        this.refreshEnabled = refreshEnabled
        this.loadMoreEnabled = loadMoreEnabled
        this.refreshTrigger = refreshTriggerPx
        this.indicatorHeight = indicationHeightPx
    }

    BoxWithConstraints(modifier.nestedScroll(connection = nestedScrollConnection)) {
        if (!state.isSwipeInProgress)
            LaunchedEffect((state.loadState == REFRESHING || state.loadState == LOADING_MORE)) {
                //回弹动画
                animate(
                    animationSpec = tween(durationMillis = 300),
                    initialValue = state.progress.offset,
                    targetValue = when (state.loadState) {
                        LOADING_MORE -> indicationHeightPx
                        REFRESHING -> indicationHeightPx
                        else -> 0f
                    }
                ) { value, _ ->
                    if (!state.isSwipeInProgress) {
                        state.progress = state.progress.copy(
                            offset = value,
                            fraction = min(1f, value / refreshTriggerPx)
                        )
                    }
                }
            }

        val offsetDp = with(LocalDensity.current) {
            state.progress.offset.toDp()
        }
        //子可组合项 根据state.progress来设置子可组合项的padding
        content(
            modifier = when (state.progress.location) {
                TOP -> Modifier.padding(top = offsetDp)
                BOTTOM -> Modifier.padding(bottom = offsetDp)
                else -> Modifier
            }
        )
        if (state.progress.location != NONE) {
            //顶部、底部的indicator 纵坐标跟随state.progress移动
            Box(modifier = Modifier
                .fillMaxWidth()
                .height(refreshTriggerDistance)
                .graphicsLayer {
                    translationY =
                        if (state.progress.location == LOADING_MORE) constraints.maxHeight - state.progress.offset
                        else state.progress.offset - refreshTriggerPx
                }
            ) {
                indicator(
                    Modifier.align(if (state.progress.location == TOP) Alignment.BottomStart else Alignment.TopStart),
                    state = state,
                    indicatorHeight = indicationHeight
                )
            }
        }
    }
}


@Preview(showBackground = true, showSystemUi = true)
@Composable
fun MySwipeRefreshPreview() {
    val scope = rememberCoroutineScope()
    val state = rememberMySwipeRefreshState(NORMAL)

    var list by remember {
        mutableStateOf(emptyList<String>())
    }

    // 模拟初始化加载数据
    LaunchedEffect(Unit) {
        state.loadState = REFRESHING
        delay(2000)
        list = List(20) { "I'm item $it" }
        state.loadState = NORMAL
    }

    MySwipeRefresh(
        state = state,
        indicator = { modifier, s, indicatorHeight ->
            LoadingIndicatorSample(modifier, s, indicatorHeight)
        },
        onRefresh = {
            scope.launch {
                state.loadState = REFRESHING
                //模拟网络请求
                delay(500)
                list = List(20) { "I'm item $it" }
                state.loadState = NORMAL
            }
        },
        onLoadMore = {
            scope.launch {
                state.loadState = LOADING_MORE
                //模拟网络请求
                delay(500)
                list = list + List(20) { "I'm item ${it + list.size}" }
                state.loadState = NORMAL
            }
        }
    ) { modifier ->
        // 注意这里要把modifier设置过来，要不然LazyColumn不会跟随它上下拖动
        LazyColumn(modifier) {
            items(items = list, key = { it }) {
                Text(
                    text = it,
                    Modifier
                        .fillMaxWidth()
                        .padding(10.dp)
                )
            }
        }
    }

}
